package com.lingjtx.common.core.token;

import com.alibaba.fastjson2.JSON;
import com.lingjtx.common.core.RedisOptions;
import com.lingjtx.common.core.algorithm.AESUtil;
import com.lingjtx.common.core.constant.Const;
import com.lingjtx.common.core.constant.RedisConfig;
import com.lingjtx.common.core.util.StringUtil;

import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Collectors;


public class AuthToken {

    public static final String BASE64URL = "base64";

    public static final String REPLACE_URL = "replace";

    public static final String DEFAULT_METHOD = REPLACE_URL;

    public static UserVo createToken(UserVo user, String secret, RedisOptions redisOptions) {
        // operationDevice=4 代表密钥授权登录
        return createToken(user, OperationDevice.APP_KEY.getCode(), secret, redisOptions, false);
    }

    public static UserVo createToken(UserVo user, int operationDevice, String deviceUid, RedisOptions redisOptions, boolean offline) {
        UserDetail userDetail = new UserDetail();
        userDetail.setUserId(user.getUserId());
        userDetail.setUserType(user.getUserType());
        userDetail.setCertificationStatus(user.getCertificationStatus());
        userDetail.setRole(user.getAdminAccount());
        userDetail.setOperationDevice(operationDevice);
        userDetail.setDeviceUid(deviceUid);
        userDetail.setOffline(offline);
        userDetail.setCompanyId(user.getCompanyId());
        String accessToken = generateToken(userDetail, DEFAULT_METHOD);
        user.setToken(accessToken);
        return rdsOperation(user, redisOptions);
    }

    public static UserVo rdsOperation(UserVo user, RedisOptions redis) {
        // token设置为单账号在线，账号新登录，之前账号即下线，token缓存删除

        // 1、 先判断缓存中有没有key为uuid的token缓存，有的话删除
        // 2、 设置key为token的缓存用户信息，并设置token有效期
        // 3、 设置key为uuid的缓存token

        UserDetail userDetail = getUser(user.getToken());
        String hashKey = getRdsTokenHashKey(userDetail);
        String oldToken = (String) redis.hmGet(RedisConfig.KEY_USER_TOKEN + user.getUserId(), hashKey);
        redis.delete(oldToken);

        // 缓存用户信息
        redis.set(user.getToken(), user, RedisConfig.TIMEOUT_ACCESS_TOKEN);

        // 通过userUid 缓存accessToken
        redis.hmPut(RedisConfig.KEY_USER_TOKEN + user.getUserId(), hashKey, user.getToken());
        redis.expire(RedisConfig.KEY_USER_TOKEN + user.getUserId(), RedisConfig.TIMEOUT_REFRESH_TOKEN);
        return user;
    }

    public static UserVo refreshToken(String token, boolean offline, RedisOptions redis, UserInfoCallback callback) {
        // 短时间内不刷新token
        if (redis.exists(token)) {
            UserVo user = (UserVo) redis.get(token);
            if (Objects.nonNull(user)) {
                user.setToken(token);
            }
            return user;
        }
        UserDetail userDetail = getUser(token);
        // 刷新token时，检查refresh_token是否过期
        if (Objects.nonNull(userDetail) && redis.exists(RedisConfig.KEY_USER_TOKEN + userDetail.getUserId())) {
            // 之所以回调此方法，是因为要刷新token的时候更新缓存中用户数据
            UserVo user = callback.selectUserById(userDetail.getUserId(), userDetail.getUserType());
            if (Objects.nonNull(user)) {
                userDetail.setUserId(user.getUserId());
                userDetail.setCertificationStatus(user.getCertificationStatus());
                userDetail.setRole(user.getAdminAccount());
                userDetail.setOffline(offline);
                String accessToken = generateToken(userDetail, DEFAULT_METHOD);
                user.setToken(accessToken);
                user.setUserType(userDetail.getUserType());
                user.setCompanyId(userDetail.getCompanyId());
                return AuthToken.rdsOperation(user, redis);
            }
        }
        return null;
    }

    public static void logout(String token, RedisOptions redis) {
        UserDetail user = getUser(token);
        if (Objects.nonNull(user)) {
            redis.delete(token);
            String hashKey = getRdsTokenHashKey(user);
            redis.hmDelete(RedisConfig.KEY_USER_TOKEN + user.getUserId(), hashKey);
        }
    }

    public static UserDetail getUser(String token) {
        return getUser(token, DEFAULT_METHOD);
    }

    public static UserDetail getUser(String token, String method) {
        String sign;
        if (BASE64URL.equals(method)) {
            sign = new String(Base64.getUrlDecoder().decode(token), StandardCharsets.UTF_8);
        } else {
            sign = token.replace("-", "+")
                    .replace("_", "/")
                    .replace(".", "=");
        }
        return getUserDecrypt(sign);
    }

    /**
     * @return true 过期
     */
    public static boolean isExpTime(String token) {
        UserDetail userDetail = AuthToken.getUser(token);
        if (Objects.nonNull(userDetail)) {
            return userDetail.getExpTime().before(new Date());
        }
        return true;
    }

    /**
     * 共同在线策略：PC和手机端（安卓，iOS，小程序）可以共同在线，手机端则互踢
     */
    public static String getRdsTokenHashKey(UserDetail userDetail) {
        OperationDevice operationDevice = OperationDevice.of(userDetail.getOperationDevice());
        String hashKey;
        if (OperationDevice.ANDROID.equals(operationDevice)
                || OperationDevice.IOS.equals(operationDevice)
                || OperationDevice.MINI.equals(operationDevice)) { // 手机端
            hashKey = userDetail.getOffline() ? "mobile" : userDetail.getDeviceUid();
        } else { // 其他端
            hashKey = userDetail.getOffline() ? operationDevice.toString() : userDetail.getDeviceUid();
        }
        return hashKey;
    }

    /**
     * 获取登录设备列表
     */
    public static List<UserDetail> getLoginDeviceList(Long userId, RedisOptions redis) {
        List<Object> list = redis.hmValues(RedisConfig.KEY_USER_TOKEN + userId);
        if (list == null || list.isEmpty()) {
            return Collections.emptyList();
        }

        return list.stream()
                .filter(String.class::isInstance)
                .map(String.class::cast)
                .map(token -> {
                    UserDetail userDetail = AuthToken.getUser(token);
                    userDetail.setJti(token); // 用token替换UserDetail中的jti字段
                    return userDetail;
                })
                .collect(Collectors.toList());
    }

    private static String generateToken(UserDetail userDetail) {
        userDetail.setJti(UUID.randomUUID().toString());
        userDetail.setExpTime(new Date(System.currentTimeMillis() + RedisConfig.TIMEOUT_ACCESS_TOKEN * 1000));
        return AESUtil.encrypt(JSON.toJSONString(userDetail), Const.MD5_SALT);
    }

    private static String generateToken(UserDetail userDetail, String method) {
        String token = generateToken(userDetail);
        if (BASE64URL.equals(method)) {
            return Base64.getUrlEncoder().withoutPadding().encodeToString(token.getBytes(StandardCharsets.UTF_8));
        }
        return token.replace("+", "-")
                .replace("/", "_")
                .replace("=", ".");
    }

    private static UserDetail getUserDecrypt(String token) {
        String json = AESUtil.decrypt(token, Const.MD5_SALT);
        if (StringUtil.isNotEmpty(json)) {
            return JSON.parseObject(json, UserDetail.class);
        }
        return null;
    }
}
